﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;

namespace SBToolkit.Text
{
    /// <summary>
    /// Provides named formating capabilities.
    /// </summary>
    public static class StringNamedFormat
    {
        /// <summary>
        /// Formats specified source using named arguments.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="source">The source to format. Can be used complex object as well.</param>
        /// <returns>Formated source.</returns>
        public static string NamedFormat(this string format, object source)
        {
            return NamedFormat(format, null, source);
        }

        /// <summary>
        /// Formats specified source using named arguments.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="provider">Format item to use.</param>
        /// <param name="source">The source to format. Can be used complex object as well.</param>
        /// <returns>Formated source.</returns>
        public static string NamedFormat(this string format, IFormatProvider provider, object source)
        {
            if (format == null)
            {
                throw new ArgumentNullException("format");
            }

            var values = new List<object>();
            string rewrittenFormat = Regex.Replace(format, @"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
                delegate(Match m)
                {
                    Group startGroup = m.Groups["start"];
                    Group propertyGroup = m.Groups["property"];
                    Group formatGroup = m.Groups["format"];
                    Group endGroup = m.Groups["end"];

                    values.Add((propertyGroup.Value == "0") ? source : Eval(source, propertyGroup.Value));

                    int openings = startGroup.Captures.Count;
                    int closings = endGroup.Captures.Count;

                    string result;
                    if (openings > closings || openings % 2 == 0)
                    {
                        result = m.Value;
                    }
                    else
                    {
                        result = new string('{', openings) + (values.Count - 1) + formatGroup.Value + new string('}', closings);
                    }

                    return result;
                },
                RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

            return string.Format(provider, rewrittenFormat, values.ToArray());
        }

        /// <summary>
        /// Evaluates source by using specified expression.
        /// </summary>
        /// <param name="source">The source to evaluate.</param>
        /// <param name="expression">The expression to use.</param>
        /// <returns>Result of evaluation.</returns>
        private static object Eval(object source, string expression)
        {
            try
            {
                return DataBinder.Eval(source, expression);
            }
            catch (HttpException e)
            {
                throw new FormatException(null, e);
            }
        }
    }
}